﻿using System;
using System.Collections.Generic;
using MockingBird.Simulator.Framework.Exceptions;

namespace MockingBird.Simulator.Framework
{
    using System.Diagnostics;
    using System.Threading;
    using Configuration;
    using Interfaces;
    using MockingBird.Common.Infrastructure.Logging;

    public class OperationNameInferenceEngine
    {
        // dependencies.
        private IExecutionContext context;
        private IHandlerConfigurationManager configManager;
        private ILogger logger;

        // private members.
        private string locatedOperationName;
        private OperationInferenceStrategy inferenceStrategy;
        private string currentSoapAction;
        private Uri currentSoapActionUri;

        #region "constructor(s)"

        public OperationNameInferenceEngine(IHandlerConfigurationManager configurationManager)
        {
            this.configManager = configurationManager;

        }

        public OperationNameInferenceEngine(IExecutionContext executionContext, IHandlerConfigurationManager configurationManager, ILogger logger)
        {
            Debug.WriteLine(string.Format("OperationNameInferenceEngine.ctor on thread ID: {0}", Thread.CurrentThread.ManagedThreadId));

            this.context = executionContext;
            this.configManager = configurationManager;
            this.logger = logger;
            this.currentSoapAction = this.context.Action;

            this.locatedOperationName = String.Empty;
            this.currentSoapActionUri = null;

        }
        #endregion

        #region "public methods"
        // The public methods are also useful for the studio

        public string InferCurrentOperationName()
        {
            this.SetInferenceStrategy();
            this.LocateOperationUsingStrategy();
            return this.locatedOperationName;
        }

        public string GetOperationNameFromSoapAction(string soapAction)
        {
            this.GetOperationNameFromSoapActionInternal(soapAction);
            return locatedOperationName;
        }

        public string GetOperationNameFromQueryString(string queryString)
        {
            this.GetOperationNameFromQueryStringInternal(queryString);
            return locatedOperationName;
        }

        public string GetOperationNameFromMappedSoapAction(string soapAction)
        {
            this.GetOperationNameFromMappedSoapActionInternal(soapAction);
            return locatedOperationName;
        }

        public string GetOperationNameFromMappedRootElement(string rootElement)
        {
            this.GetOperationNameFromMappedRootElementInternal(rootElement);
            return locatedOperationName;
        }


        #endregion

        #region "private methods"

        private void SetInferenceStrategy()
        {
            this.inferenceStrategy = (OperationInferenceStrategy)
                Enum.Parse(typeof(OperationInferenceStrategy),
                                           configManager.GetOperationInferenceStrategy());
        }

        private void LocateOperationUsingStrategy()
        {
            switch (inferenceStrategy)
            {
                case OperationInferenceStrategy.SoapAction:
                    logger.Log("Inferring operation name from soapAction");
                    this.GetOperationNameFromSoapActionInternal(this.currentSoapAction);
                    break;

                case OperationInferenceStrategy.QueryString:
                    logger.Log("Inferring operation name from querystring");
                    this.GetOperationNameFromQueryStringInternal(this.context.Uri.Query);
                    break;

                case OperationInferenceStrategy.SoapActionOperationMap:
                    logger.Log("Inferring operation name from map of soap actions");
                    this.GetOperationNameFromMappedSoapActionInternal(this.currentSoapAction);
                    break;

                case OperationInferenceStrategy.RequestRootElementOperationMap:
                    logger.Log("Inferring operation name from map of request root elements");
                    this.GetOperationNameFromMappedRootElementInternal(this.context.RequestRootElementName);
                    break;

                default:
                    logger.LogInfo("Default condition of inference strategy. Will now infer operation name from soapAction");
                    this.GetOperationNameFromSoapActionInternal(this.currentSoapAction);
                    break;
            }

        }

        private void GetOperationNameFromSoapActionInternal(string soapAction)
        {
            string errorMessage = String.Empty;

            this.ValidateSoapAction(soapAction);
            this.HandleSpecialSoapActionFormatting(ref soapAction);
            this.currentSoapAction = soapAction;
            this.CreateActionUri();
            // Now infer the current operation name from the final token of the soapAction
            this.locatedOperationName = this.currentSoapActionUri.Segments[this.currentSoapActionUri.Segments.Length - 1];
            this.RemoveSpecialCharacters();
        }



        private void ValidateSoapAction(string soapAction)
        {
            if (String.IsNullOrEmpty(soapAction))
            {
                throw new InvalidOperationException("Inference strategy is SoapAction but SoapAction is null or empty.Cannot proceed");
            }
        }

        /// <summary>
        /// This method is used specially for BizTalk clients that add the \ to the beginning and end of the soap action
        /// </summary>
        /// <param name="originalSoapAction"></param>
        /// <returns></returns>
        private void HandleSpecialSoapActionFormatting(ref string originalSoapAction)
        {
            originalSoapAction.Replace("\"", string.Empty);
        }

        private void CreateActionUri()
        {
            string errorMessage;
            try
            {

                this.currentSoapActionUri = new Uri(this.currentSoapAction);
            }
            catch (Exception e)
            {
                errorMessage = String.Format("Could not create Uri from the given soapAction : {0} ", this.currentSoapAction);

                throw new InvalidOperationException(errorMessage);
            }
        }


        private void RemoveSpecialCharacters()
        {
            // in some sample wsdls there was a special character prior to the operation name
            // if such a character exists, then remove it. 
            // TODO: This needs to be extended for different special characters
            // and preferable to be done via an OperationNameFormatter class or something like that !
            // we can use the idea of a chain of decorators from WSCF and add formatters.

            if (this.locatedOperationName.Contains(":"))
            {
                this.locatedOperationName = this.locatedOperationName.Replace(":", "");
            }
            if (this.locatedOperationName.Contains("#"))
            {
                this.locatedOperationName = this.locatedOperationName.Replace("#", "");
            }
        }


        private void GetOperationNameFromQueryStringInternal(string query)
        {
            this.ValidateQueryString(query);
            //as we are enforcing a pattern for this handler simply do a string replace on the pattern ?op=
            locatedOperationName = query.Replace("?op=", "");
            //TODO:Should we do any tests after the replace
        }

        private void ValidateQueryString(string query)
        {
            if (String.IsNullOrEmpty(query))
            {
                throw new InvalidOperationException("Inference strategy is QueryString but QueryString is null or empty.Cannot proceed");
            }
        }

        private void GetOperationNameFromMappedSoapActionInternal(string soapAction)
        {
            // Get the actionMap from the config manager
            Dictionary<string, string> actionMap = this.configManager.GetActionMap();

            // Now lookup the current soap action in the dictionary
            bool operationFound = actionMap.TryGetValue(soapAction, out this.locatedOperationName);
            if (!operationFound)
            {
                throw new ApplicationConfigurationException("Could not find an operation corresponding to the provided soap action :" + this.currentSoapAction);
            }
        }

        private void GetOperationNameFromMappedRootElementInternal(string rootElementName)
        {
            // Get the request map from the config manager
            Dictionary<string, string> requestMap = this.configManager.GetRequestMap();

            // Now lookup the request root element name (from context) in the dictionary
            bool operationFound = requestMap.TryGetValue(
                                            rootElementName,
                                            out this.locatedOperationName);

            if (!operationFound)
            {
                throw new ApplicationConfigurationException("Could not find an operation corresponding to the request root element name :" + this.context.RequestRootElementName);
            }
        }

        #endregion
    }
}
